package com.demo.project_template.advice;



import com.demo.project_template.annotation.RateLimiter;
import com.demo.project_template.constant.CommonConstants;
import com.demo.project_template.exception.ErrorCodeException;
import com.demo.project_template.exception.ErrorCodeExceptionEnum;
import com.demo.project_template.util.CheckEmptyUtil;
import com.demo.project_template.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;


/**
 * @Author ZhangKeChen  🤙🤙🏼🤙🏽
 * @Date 2022/11/28
 * @Description 限流注解实现类
 */

@Slf4j
@Aspect
@Component
public class RateLimiterAspect {
    @Resource
    private RedisUtil redisUtil;

    @Pointcut("@annotation(com.demo.project_template.annotation.RateLimiter)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);
        String limiterKey = rateLimiter.limiterKey();
        long count = rateLimiter.count();
        long expire = rateLimiter.expire();
        log.info("limiterKey:[{}],count:[{}],expire:[{}]", limiterKey, count, expire);
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        String headLimiterKey = request.getHeader(limiterKey);
        if (CheckEmptyUtil.isEmpty(headLimiterKey)) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.SYSTEM_ERROR);
        }
        String requestUrl = request.getRequestURI();
        if (CheckEmptyUtil.isEmpty(requestUrl)) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.SYSTEM_ERROR);
        }
        log.info("requestUrl：[{}]", requestUrl);
        if (!limiterMethod(requestUrl, limiterKey, headLimiterKey, count, expire)) {
            throw new ErrorCodeException(ErrorCodeExceptionEnum.SYSTEM_BUSY);
        }
        return joinPoint.proceed();

    }

    /**
     * 限流方法
     */
    public Boolean limiterMethod(String requestUrl, String limiterKey, String headLimiterKey, long count, long expire) {
        StringBuilder redisKey = new StringBuilder(CommonConstants.LIMITER_REDIS_KEY).append(requestUrl).append(":").append(limiterKey).append(":").append(headLimiterKey);
        String redisCount = redisUtil.get(String.valueOf(redisKey));
        if (CheckEmptyUtil.isEmpty(redisCount)) {
            redisUtil.set(String.valueOf(redisKey), String.valueOf(1), expire);
            return true;
        }
        if (Long.parseLong(redisCount) < count) {
            redisUtil.incr(String.valueOf(redisKey), 1);
            return true;

        } else {
            return false;

        }

    }
}
